// ======================================================================
// \title  Health.hpp
// \author Tim
// \brief  hpp file for Health component implementation class
//
// \copyright
// Copyright 2009-2015, by the California Institute of Technology.
// ALL RIGHTS RESERVED.  United States Government Sponsorship
// acknowledged.
//
// ======================================================================

#include <Fw/FPrimeBasicTypes.hpp>
#include <Fw/Types/Assert.hpp>
#include <Svc/Health/HealthComponentImpl.hpp>

namespace Svc {

// ----------------------------------------------------------------------
// Construction, initialization, and destruction
// ----------------------------------------------------------------------

HealthImpl::HealthImpl(const char* const compName)
    : HealthComponentBase(compName),
      m_numPingEntries(0),
      m_key(0),
      m_watchDogCode(0),
      m_warnings(0),
      m_enabled(Fw::Enabled::ENABLED),
      queue_depth(0) {
    static_assert((HealthComponentBase::NUM_PINGSEND_OUTPUT_PORTS >= 0) &&
                      (HealthComponentBase::NUM_PINGSEND_OUTPUT_PORTS <= std::numeric_limits<FwIndexType>::max()),
                  "NUM_PINGSEND_OUTPUT_PORTS must fit in the positive range of FwIndexType");
    // clear tracker by disabling pings
    for (FwIndexType entry = 0; entry < static_cast<FwIndexType>(FW_NUM_ARRAY_ELEMENTS(this->m_pingTrackerEntries));
         entry++) {
        this->m_pingTrackerEntries[entry].enabled = Fw::Enabled::DISABLED;
    }
}

void HealthImpl::init(const FwSizeType queueDepth, const FwEnumStoreType instance) {
    HealthComponentBase::init(queueDepth, instance);
    this->queue_depth = queueDepth;
}

void HealthImpl::setPingEntries(PingEntry* pingEntries, FwIndexType numPingEntries, U32 watchDogCode) {
    FW_ASSERT(pingEntries);
    // make sure not asking for more pings than ports
    FW_ASSERT(numPingEntries <= NUM_PINGSEND_OUTPUT_PORTS);

    this->m_numPingEntries = numPingEntries;
    this->m_watchDogCode = watchDogCode;

    // copy entries to private data
    for (FwIndexType entry = 0; entry < numPingEntries; entry++) {
        FW_ASSERT(pingEntries[entry].warnCycles <= pingEntries[entry].fatalCycles,
                  static_cast<FwAssertArgType>(pingEntries[entry].warnCycles),
                  static_cast<FwAssertArgType>(pingEntries[entry].fatalCycles));
        this->m_pingTrackerEntries[entry].entry = pingEntries[entry];
        this->m_pingTrackerEntries[entry].cycleCount = 0;
        this->m_pingTrackerEntries[entry].enabled = Fw::Enabled::ENABLED;
        this->m_pingTrackerEntries[entry].key = 0;
    }
}

HealthImpl::~HealthImpl() {}

// ----------------------------------------------------------------------
// Handler implementations for user-defined typed input ports
// ----------------------------------------------------------------------

void HealthImpl::PingReturn_handler(const FwIndexType portNum, U32 key) {
    // verify the key value
    if (key != this->m_pingTrackerEntries[portNum].key) {
        Fw::LogStringArg _arg = this->m_pingTrackerEntries[portNum].entry.entryName;
        this->log_FATAL_HLTH_PING_WRONG_KEY(_arg, key);
    } else {
        // reset the counter and clear the key
        this->m_pingTrackerEntries[portNum].cycleCount = 0;
        this->m_pingTrackerEntries[portNum].key = 0;
    }
}

void HealthImpl::Run_handler(const FwIndexType portNum, U32 context) {
    // dispatch messages
    for (FwSizeType i = 0; i < this->queue_depth; i++) {
        MsgDispatchStatus stat = this->doDispatch();
        if (MSG_DISPATCH_EMPTY == stat) {
            break;
        }
        FW_ASSERT(MSG_DISPATCH_OK == stat);
    }

    if (this->m_enabled == Fw::Enabled::ENABLED) {
        // cycle through ping table, pinging ports that are not awaiting a reply
        // for ports that are awaiting a reply, decrement their counters
        // and check for violations

        for (FwIndexType entry = 0; entry < this->m_numPingEntries; entry++) {
            if (Fw::Enabled::ENABLED == this->m_pingTrackerEntries[entry].enabled) {
                // If clear entry
                if (0 == this->m_pingTrackerEntries[entry].cycleCount) {
                    // start a ping
                    this->m_pingTrackerEntries[entry].key = this->m_key;
                    // send ping
                    this->PingSend_out(static_cast<FwIndexType>(entry), this->m_pingTrackerEntries[entry].key);
                    // increment key
                    this->m_key++;
                    // increment cycles for the entry
                    this->m_pingTrackerEntries[entry].cycleCount++;
                } else {
                    // check to see if it is at warning threshold
                    if (this->m_pingTrackerEntries[entry].cycleCount ==
                        this->m_pingTrackerEntries[entry].entry.warnCycles) {
                        Fw::LogStringArg _arg = this->m_pingTrackerEntries[entry].entry.entryName;
                        this->log_WARNING_HI_HLTH_PING_WARN(_arg);
                        this->tlmWrite_PingLateWarnings(++this->m_warnings);
                    } else {
                        // check for FATAL timeout value
                        if (this->m_pingTrackerEntries[entry].entry.fatalCycles ==
                            this->m_pingTrackerEntries[entry].cycleCount) {
                            Fw::LogStringArg _arg = this->m_pingTrackerEntries[entry].entry.entryName;
                            this->log_FATAL_HLTH_PING_LATE(_arg);
                        }
                    }  // if at warning or fatal threshold

                    this->m_pingTrackerEntries[entry].cycleCount++;
                }  // if clear entry
            }  // if entry has ping enabled
        }  // for each entry

        // do other specialized platform checks (e.g. VxWorks suspended tasks)
        this->doOtherChecks();

    }  // If health checking is enabled

    // stroke watchdog.
    if (this->isConnected_WdogStroke_OutputPort(0)) {
        this->WdogStroke_out(0, this->m_watchDogCode);
    }
}

// ----------------------------------------------------------------------
// Command handler implementations
// ----------------------------------------------------------------------

void HealthImpl::HLTH_ENABLE_cmdHandler(const FwOpcodeType opCode, U32 cmdSeq, Fw::Enabled enable) {
    this->m_enabled = enable;
    Fw::Enabled isEnabled = Fw::Enabled::DISABLED;
    if (enable == Fw::Enabled::ENABLED) {
        isEnabled = Fw::Enabled::ENABLED;
    }
    this->log_ACTIVITY_HI_HLTH_CHECK_ENABLE(isEnabled);
    this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}

void HealthImpl::HLTH_PING_ENABLE_cmdHandler(const FwOpcodeType opCode,
                                             U32 cmdSeq,
                                             const Fw::CmdStringArg& entry,
                                             Fw::Enabled enable) {
    // check to see if entry is in range
    FwIndexType entryIndex = this->findEntry(entry);

    if (-1 == entryIndex) {
        this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR);
        return;
    }

    this->m_pingTrackerEntries[entryIndex].enabled = enable.e;
    Fw::Enabled isEnabled(Fw::Enabled::DISABLED);
    if (enable == Fw::Enabled::ENABLED) {
        isEnabled = Fw::Enabled::ENABLED;
    }
    Fw::LogStringArg arg;
    arg = entry;
    this->log_ACTIVITY_HI_HLTH_CHECK_PING(isEnabled, arg);
    this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}

void HealthImpl::HLTH_CHNG_PING_cmdHandler(const FwOpcodeType opCode,
                                           U32 cmdSeq,
                                           const Fw::CmdStringArg& entry,
                                           U32 warningValue,
                                           U32 fatalValue) {
    // check to see if entry is in range
    FwIndexType entryIndex = this->findEntry(entry);
    if (-1 == entryIndex) {
        this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR);
        return;
    }

    // check to see if warningValue less than or equal to fatalValue
    if (warningValue > fatalValue) {
        Fw::LogStringArg arg;
        arg = entry;
        this->log_WARNING_HI_HLTH_PING_INVALID_VALUES(arg, warningValue, fatalValue);
        this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR);
        return;
    }

    this->m_pingTrackerEntries[entryIndex].entry.warnCycles = warningValue;
    this->m_pingTrackerEntries[entryIndex].entry.fatalCycles = fatalValue;
    Fw::LogStringArg arg = entry;
    this->log_ACTIVITY_HI_HLTH_PING_UPDATED(arg, warningValue, fatalValue);
    this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}

FwIndexType HealthImpl::findEntry(const Fw::CmdStringArg& entry) {
    static_assert(std::numeric_limits<FwIndexType>::is_signed, "FwIndexType must be signed to return -1 for error");
    // walk through entries
    for (FwIndexType tableEntry = 0; tableEntry < NUM_PINGSEND_OUTPUT_PORTS; tableEntry++) {
        if (entry == this->m_pingTrackerEntries[tableEntry].entry.entryName) {
            return static_cast<FwIndexType>(tableEntry);
        }
    }
    Fw::LogStringArg arg = entry;
    this->log_WARNING_LO_HLTH_CHECK_LOOKUP_ERROR(arg);

    return -1;
}

}  // end namespace Svc
